/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#include "pch.h"
#include <app/journal_app.h>
#include <app/constants.h>
#include <models/interfaces.h>
#include <views/interfaces.h>
#include <common/settings_service.hpp>
#include <journal/common/utils.hpp>
#include <app/keys.h>
#include <baja/app/debug_console_trace.hpp>
#include <journal/views/shared.hpp>
#include <journal/common/storage_service.h>
#include <journal/common/automation_service.h>
#include <journal/common/memory_tracking_service.h>
#include <baja/transition_tracking/transition_tracking_service.h>
#include <baja/stream/storage_file.h>

namespace journal {

namespace b = baja;
namespace g = baja::graphics;
namespace bt = baja::transition_tracking;
namespace jsa = journal::services::automation;
namespace jsm = journal::services::memory_tracking;
namespace c = concurrency;

journal_app::winrt_features::winrt_features(
    const std::shared_ptr<view::ishare_source>& source,
    const std::shared_ptr<view::iphoto_sink>& photoSink
    ) : _source(source), _photoSink(photoSink)
{
}

// winrt features 
void journal_app::winrt_features::initializeSharing()
{
    auto dataTransferManager = DataTransferManager::GetForCurrentView();

    dataTransferManager->DataRequested += ref new TypedEventHandler<DataTransferManager^, DataRequestedEventArgs^>(
        this, 
        &journal_app::winrt_features::onDataRequested
        );
}

void journal_app::winrt_features::streamFromBlob(
    const std::shared_ptr<view::imemory_blob>& blob, 
    std::function<void(RandomAccessStreamReference^)> f
    )
{
    // buncha winrt goop just to get a silly stream.  i am sure this can be done without copying memory over 20 times
    InMemoryRandomAccessStream^ stream = ref new InMemoryRandomAccessStream();
    IOutputStream^ outputStream = stream->GetOutputStreamAt(0);
    DataWriter^ writer = ref new DataWriter(stream);
    Platform::Array<b::byte>^ buffer = ref new Platform::Array<b::byte>(static_cast<uint32>(blob->count()));
    std::memcpy(buffer->Data, blob->bytes(), blob->count());
    writer->WriteBytes(buffer);

    size_t s = buffer->Length;
    b::byte* b = buffer->Data;

    c::task<unsigned int> t = c::task<unsigned int>(writer->StoreAsync());

    t.then([stream, f](unsigned int i)
    {
        RandomAccessStreamReference^ streamReference= RandomAccessStreamReference::CreateFromStream(stream);

        f(streamReference);
    });
}

void journal_app::winrt_features::onDataRequested(DataTransferManager^ sender, DataRequestedEventArgs^ args)
{
    DataRequest^ request = args->Request;

    // It's important to call this here it will tell the API we'll be async
    // (this is terrible API design if you ask me)
    DataRequestDeferral^ deferral = request->GetDeferral();

    ACQUIRE_VIEW_LOCK;
    view::copy_info info;
    info.selection = false;  // this may get set later down the chain

	_source->onShareImageAsync(
		info,
        [this, request, deferral](const std::shared_ptr<view::ishared_image>& image)
        {
            this->streamFromBlob(
                image->image(),
                [image, request, deferral](RandomAccessStreamReference^ imageStreamReference)
                {
                        StorageFolder^ f = Windows::Storage::ApplicationData::Current->LocalFolder;
                        c::task<StorageFile^> t(
                            f->GetFileAsync(ref new String(image->title().c_str()))
                            );

                        t.then([image, request, deferral](StorageFile^ sf)
                        {
// TRACK: uncomment this to share the page as a bitmap
// the email app doesn't support this so right now we are sharing it as a file
#if 0
                            request->Data->Properties->ApplicationName = constants::general::applicationName();
                            request->Data->Properties->Title = ref new String(h->image->title().c_str());
                            request->Data->Properties->Thumbnail = h->thumbnailStreamReference;
                            request->Data->SetBitmap(h->imageStreamReference);
                            h->deferral->Complete();
#endif

                            Platform::Collections::Vector<IStorageItem^> v;
                            v.Append(sf);
                            request->Data->Properties->ApplicationName = ref new String(constants::general::applicationName());
                            request->Data->Properties->Title = ref new String(image->title().c_str());
                            request->Data->Properties->Thumbnail =  RandomAccessStreamReference::CreateFromFile(sf);

                            IVectorView<IStorageItem^>^ vv = v.GetView();
                            IIterable<IStorageItem^>^ it = dynamic_cast<IIterable<IStorageItem^>^>(vv);
                            request->Data->SetStorageItems(it);
                            deferral->Complete();
                        });
                });
        });
}

void journal_app::winrt_features::copy(const bool selection)
{
    view::copy_info info;
    info.selection = selection;

    ACQUIRE_VIEW_LOCK;
    _source->onCopyImageAsync(
        info,
        [this](const std::shared_ptr<view::ishared_image>& image)
        {
            this->streamFromBlob(
                image->image(),
                [image](RandomAccessStreamReference^ imageStreamReference)
                {
                        StorageFolder^ f = Windows::Storage::ApplicationData::Current->LocalFolder;
                        c::task<StorageFile^> t(
                            f->GetFileAsync(ref new String(image->title().c_str()))
                            );

                        t.then([image](StorageFile^ sf)
                        {
                            DataPackage^ package = ref new DataPackage();
                            package->RequestedOperation = Windows::ApplicationModel::DataTransfer::DataPackageOperation::Copy;

                            package->SetBitmap(RandomAccessStreamReference::CreateFromFile(sf));
                            
                            Windows::ApplicationModel::DataTransfer::Clipboard::SetContent(package);

                            // refresh the app bar so the "paste" button is refreshed
                            constants::uiService()->refreshAppBar();
                        });
                });
        });
}

void journal_app::winrt_features::paste()
{
    DataPackageView^ packageView = Windows::ApplicationModel::DataTransfer::Clipboard::GetContent();

    std::shared_ptr<DataReader^> dataReader = std::make_shared<DataReader^>(nullptr);

    if (packageView->Contains(Windows::ApplicationModel::DataTransfer::StandardDataFormats::Bitmap))
    {
        c::task<RandomAccessStreamReference^> t(packageView->GetBitmapAsync());

        t.then([](RandomAccessStreamReference^ streamReference) -> IAsyncOperation<IRandomAccessStreamWithContentType^>^
        {
            return streamReference->OpenReadAsync();
        }).then([dataReader](IRandomAccessStreamWithContentType^ stream) -> IAsyncOperation<unsigned int>^
        {
            *dataReader = ref new DataReader(stream);

            return (*dataReader)->LoadAsync(static_cast<unsigned int>(stream->Size));
        }).then([this, dataReader](c::task<unsigned int> t)
        {
            unsigned int size = t.get();

            Platform::Array<b::byte>^ buffer = ref new Platform::Array<b::byte>(size);
            std::shared_ptr<winrt_memory_blob> blob = std::make_shared<winrt_memory_blob>(buffer);

            (*dataReader)->ReadBytes(buffer);

            std::shared_ptr<view::ishared_image> sharedImage = std::make_shared<view::shared_image>(
                nullptr, // thumbnail
                blob,
                constants::general::sharedPageFileName()
                );

            ACQUIRE_VIEW_LOCK;
            _source->onPasteImageAsync(sharedImage, nullptr);
        });
    }
}

void journal_app::winrt_features::selectFilesystemPhoto()
{
    std::vector<std::wstring> fileTypeFilter;

    std::for_each(
        constants::general::imageFormats().begin(),
        constants::general::imageFormats().end(),
        [&](const std::wstring& s)
    {
        fileTypeFilter.push_back(s);
    });

    std::shared_ptr<b::storage::istorage_file> storageFile = b::storage::createStorageFile();

    storageFile->initializeFromPickerAsync(
            b::storage::known_folders::picture_library,
            fileTypeFilter,
            [this, storageFile](bool success, bool cancelled)
            {
                if (!success)
                {
                    BAJA_THROW(std::logic_error("an error occured while executing initializeFromPickerAsync()"));
                }
                if (!cancelled)
                {
                    ACQUIRE_VIEW_LOCK;
                    this->_photoSink->onPhotoFileSelected(storageFile);
                }
            }
        );
}

void journal_app::winrt_features::selectCameraPhoto()
{
    CameraCaptureUI^ capture = ref new CameraCaptureUI();

    c::task<StorageFile^> t(capture->CaptureFileAsync(CameraCaptureUIMode::Photo));

    t.then([this](c::task<StorageFile^> sft)
    {
        StorageFile^ sf; 
        try
        {
            sf = sft.get();
        }
        catch (Platform::Exception^ e)
        {
            BAJA_THROW(std::logic_error("Failed to get photo from camera"));
        }

        if (sf != nullptr)
        {
            std::shared_ptr<b::storage::istorage_file> file = b::storage::createStorageFile(sf);
            file->initializeAsync(
                    [this, file](bool success)
                    {
                        if (!success)
                        {
                            BAJA_THROW(std::logic_error("an error occured while executing istorage_file::initializeAsync()"));
                        }
                        
                        ACQUIRE_VIEW_LOCK;
                        this->_photoSink->onPhotoFileSelected(file);
                    }
                );
        }
    });
}

journal_app::journal_app(
    const std::shared_ptr<a::iloopless_app_shell>& shell,
    const std::shared_ptr<a::igraphics_factory>& graphicsFactory
    ) :
    _shell(shell), 
    _graphicsFactory(graphicsFactory),
    _frameCount(0),
    _isVisible(true)
{
}

void journal_app::render(const std::shared_ptr<g::iengine>& graphics)
{
    _root->onRender();
}

void journal_app::initialize()
{
    srand(static_cast<unsigned int>(time(nullptr)));

    // create graphics
    _graphics = _graphicsFactory.lock()->createGraphics(std::dynamic_pointer_cast<a::iapp_shell_base>(_shell.lock()), constants::general::useWARP());

    b::services::set<journal_app>(_graphics);

    // create render service
    _renderService = g::createRenderService();
    b::services::set<journal_app>(_renderService);

    // create var console and root var group
    _varRoot = b::createVarGroup(L"root");
    _varConsole = a::createVarConsole(_varRoot);
    _varRoot->addChildGroup(_graphics->getVarGroup());
    b::services::set<journal_app>(_varConsole);

    // add var console's render object
    _renderService->addRenderObject(std::dynamic_pointer_cast<g::irender_object>(_varConsole));

    // create debug console and debug var group
    _debugConsole = a::createDebugConsole(constants::references::debugConsoleLineCount());
    b::services::set<journal_app>(_debugConsole);

    // add debug console's render object
    _renderService->addRenderObject(std::dynamic_pointer_cast<g::irender_object>(_debugConsole));

    // add ourselves as a render object
    _renderService->addRenderObject(std::dynamic_pointer_cast<g::irender_object>(shared_from_this()));

    // initialize render service
    _renderService->initialize(_graphics);

    // create storage service
    _storageService = journal::services::storage::createStorageService();
    b::services::set<journal_app>(_storageService);

    // create settings service
    _settingsService = createSettingsService(_storageService->engine());
    b::services::set<journal_app>(_settingsService);

    constants::initialize();

    // create performance service
    _performanceService = createPerformanceService(_graphics);
    b::services::set<journal_app>(_performanceService);

    // create the memory tracking service
    _memoryTrackingService = jsm::createMemoryTrackingService(_graphics);
    b::services::set<journal_app>(_memoryTrackingService);

    constants::initializePerformanceService();
    constants::initializeMemoryTrackingService();
    constants::initializeStorageService();

    // initialize trace tags
    journal::app::trace_tag_registry::initialize();

    // add debug vars
    _screenParamsString.resize(max_debug_string);
    this->updateScreenParamsString();
    _debugConsole->getRoot()->addStringVar(
        L"screen params",
        _screenParamsString.data(),
        0
        );

    // capture the current render target, which should be the backbuffer in full screen
    _renderTarget = std::make_shared<g::render_target>();
    _renderTarget->snap(_graphics);

    // set up the undo/redo service
    _undoRedoService = bt::createTransitionTrackingService();
    b::services::set<journal_app>(_undoRedoService);
    constants::initializeUndoRedoService();

    // initialize key bindings
    journal::app::key_registry::initialize();

    // create the root
    _root = std::make_shared<root>(
        std::dynamic_pointer_cast<a::iinput_relay>(shared_from_this())
        );

    _winrtFeatures = ref new winrt_features(
        std::dynamic_pointer_cast<view::ishare_source>(_root),
        std::dynamic_pointer_cast<view::iphoto_sink>(_root)
        );
    _winrtFeatures->initializeSharing();

    // loading of persisted settings must be after all settings have been defined
    _settingsService->load();

    // load trace tags
    journal::app::trace_tag_registry::initialLoad();

    _automationService = jsa::createAutomationService();
    b::services::set<journal_app>(_automationService);
    constants::initializeAutomationService();

    _root->initialize();

    // this will cause the trace file to be wiped clean fresh
    DEBUG_CONSOLE_TRACE_TO_FILE(L"Application initialized");
}

void journal_app::onRender()
{
    ACQUIRE_VIEW_LOCK;

    b::stopwatch sw;
    
    if (!_isVisible) return;

    _renderTarget->apply(_graphics);

    // render the scene
    _graphics->beginScene();
    _renderService->render(_graphics); 
    _graphics->endScene();

    _graphics->showScene();

    _frameCount++;

    constants::performanceService()->collect(performance_event::renderFrame, sw.getElapsedMilliseconds());
}

void journal_app::onResize(const a::resize_args& args)
{
    ACQUIRE_VIEW_LOCK;

    _renderTarget->setRenderTarget(nullptr);
    _renderTarget->setDepthStencilTarget(nullptr);

    // first update the graphics engine on the resize
    g::display_params dp = _graphics->getDisplayParams();
    dp.backBufferWidth = args.width;
    dp.backBufferHeight = args.height;
    _graphics->changeDisplayParams(dp);

    // re-snap the render target after the resize
    _renderTarget->snap(_graphics);

    // then let the render service know, this will in turn know
    // to any observers attached to the render service
    _renderService->resize(_graphics);

    _root->onResize(args);
}

void journal_app::onPointer(const a::pointer_args& args, void* context)
{
    ACQUIRE_VIEW_LOCK;

    if (true == constants::automationService()->inProgress())
    {
        return;
    }

    b::stopwatch sw;
    DEBUG_CONSOLE_TRACE_TAG(
        TRACE_TAG_INPUT,
        boost::wformat(L"onPointer %s %s: p: %0.2f, t: %i, p: (%0.2f, %0.2f)") %
            (
                (args.deviceType == a::device_type::mouse) ? L"mouse" :
                (args.deviceType == a::device_type::touch) ? L"touch" :
                L"pen"
            ) %
            (
                (args.type == a::pointer_event_type::down) ? L"down" :
                (args.type == a::pointer_event_type::move) ? L"move" :
                L"up"
            ) %
            args.pressure %
            args.timestamp %
            args.point.x %
            args.point.y
            );

    _inputSource.raise(
        [&args](const std::shared_ptr<iinput_observer>& observer, void* context)
    {
        observer->onPointer(args, context);
    });
    constants::performanceService()->collect(performance_event::input, sw.getElapsedMilliseconds()); 
}

void journal_app::onGesture(const a::gesture_args& args, void* context)
{
    ACQUIRE_VIEW_LOCK;

    if (true == constants::automationService()->inProgress())
    {
        return;
    }

    b::stopwatch sw;
    DEBUG_CONSOLE_TRACE_TAG(
        TRACE_TAG_INPUT,
        boost::wformat(L"onGesture %s %s: e: %0.2f, r: %0.2f, t: (%0.2f, %0.2f)") %
        (
            (args.deviceType == a::device_type::mouse) ? L"mouse" :
            (args.deviceType == a::device_type::touch) ? L"touch" :
            L"pen"
        ) %
        (
            (args.type == a::gesture_event_type::start) ? L"start" :
            (args.type == a::gesture_event_type::update) ? L"update" :
            L"complete"
        ) %
        args.delta.expansion %
        args.delta.rotation %
        args.delta.translation.x %
        args.delta.translation.y
        );

        _inputSource.raise(
        [&args](const std::shared_ptr<iinput_observer>& observer, void* context)
        {
            observer->onGesture(args, context);
        });
    constants::performanceService()->collect(performance_event::input, sw.getElapsedMilliseconds()); 
}

void journal_app::onSuspend(const std::shared_ptr<a::isuspension_deferral>& deferral)
{
    ACQUIRE_VIEW_LOCK;

    DEBUG_CONSOLE_TRACE(L"journal_app::onSuspend called");
    _root->onSuspend(deferral);
}

void journal_app::onKey(const a::key_args& args, void* context)
{
    ACQUIRE_VIEW_LOCK;

    if (true == constants::automationService()->inProgress())
    {
        return;
    }

    b::stopwatch sw;
    DEBUG_CONSOLE_TRACE_TAG(
        TRACE_TAG_INPUT,
        boost::wformat(L"onKey vk: %i ('%c')") %
        args.vk %
        static_cast<b::wchar>(args.vk)
        );

    // if some UI is active let that handle the key and ignore it here
    if (constants::uiService()->isActive())
    {
        return;
    }

    if (journal::app::key_registry::check(journal::app::key::varConsole, args.vk))
    {
        _varConsole->setIsVisible(!_varConsole->getIsVisible());
    }
    else if (journal::app::key_registry::check(journal::app::key::debugConsole, args.vk))
    {
        _debugConsole->setIsVisible(!_debugConsole->getIsVisible());
    }
    else if (journal::app::key_registry::check(journal::app::key::help, args.vk))
    {
        _debugConsole->setIsVisible(true);
        journal::app::key_registry::printHelp();
    }
    else if (journal::app::key_registry::check(journal::app::key::clearDebugConsole, args.vk))
    {
        _debugConsole->clear();
    }
    else if (journal::app::key_registry::check(journal::app::key::copy, args.vk))
    {
        _winrtFeatures->copy(false); // no selection
    }
    else if (journal::app::key_registry::check(journal::app::key::paste, args.vk))
    {
        _winrtFeatures->paste();
    }
    else if (journal::app::key_registry::check(journal::app::key::undo, args.vk))
    {
        constants::undoRedoService()->undo();
    }
    else if (journal::app::key_registry::check(journal::app::key::redo, args.vk))
    {
        constants::undoRedoService()->redo();
    }

    if (_varConsole->getIsVisible())
    {
        switch (args.vk)
        {
        case (38): // up
            _varConsole->moveSelectionUp();
            break;

        case (40): // down
            _varConsole->moveSelectionDown();
            break;

        case (37): // left
            _varConsole->decrementSelection();
            // TRACK: bug 363 : need to have var_console make changes through settings
            //                 this function is only needed to support the saving of all
            //                 groups at once.
            _settingsService->save();
            break;

        case (39): // right
            _varConsole->incrementSelection();
            // TRACK: bug 363 : need to have var_console make changes through settings
            //                 this function is only needed to support the saving of all
            //                 groups at once.
            _settingsService->save();
            break;

        case (13): // enter
            _varConsole->activateSelection();
            break;

        case (8): // back
            _varConsole->navigateBack();
            break;
        
        case (27): // escape
            _varConsole->navigateBack();
            break;
        }
    }

    _inputSource.raise(
        [&args](const std::shared_ptr<iinput_observer>& observer, void* context)
    {
        observer->onKey(args, context);
    });
    constants::performanceService()->collect(performance_event::input, sw.getElapsedMilliseconds()); 
}

void journal_app::onScreenChange(const a::screen_change_args& args)
{
    ACQUIRE_VIEW_LOCK;

    DEBUG_CONSOLE_TRACE_TAG(
        TRACE_TAG_INPUT,
        boost::wformat(L"onScreenChange %s: o: %s, dpi: %0.2f") %
            (
                (args.type == a::screen_change_event_type::logicalDpi) ? L"logical dpi" :
                (args.type == a::screen_change_event_type::orientation) ? L"orientation" :
                L"n/a"
            ) %
            (
                (args.orientation == a::orientation::landscape) ? L"landscape" :
                (args.orientation == a::orientation::portrait) ? L"portrait" :
                (args.orientation == a::orientation::landscapeFlipped) ? L"landscapeFlipped" :
                (args.orientation == a::orientation::portraitFlipped) ? L"portraitFlipped" :
                L"n/a"
            ) %
            args.logicalDpi
            );

    this->updateScreenParamsString();

    _root->onScreenChange(args);
}

void journal_app::onVisibilityChanged(const bool visible)
{
    ACQUIRE_VIEW_LOCK;

    _isVisible = visible;
    _root->onVisibilityChanged(visible);
}

void journal_app::updateScreenParamsString()
{
    a::orientation o = constants::shellService()->orientation();
    b::float32 dpi = constants::shellService()->logicalDpi();

    boost::wformat fmt = 
        boost::wformat(L"orientation: %s, logical dpi: %0.2f") %
        (
            (o == a::orientation::landscape) ? L"landscape" :
            (o == a::orientation::portrait) ? L"portrait" :
            (o == a::orientation::landscapeFlipped) ? L"landscapeFlipped" :
            (o == a::orientation::portraitFlipped) ? L"portraitFlipped" :
            L"n/a"
        ) %
        dpi;

    std::wstring s = fmt.str();
    _screenParamsString.resize(s.size() + 1);
    std::copy(s.begin(), s.end(), _screenParamsString.begin());
    _screenParamsString[s.size()] = L'\0';
}

void journal_app::cleanup()
{
    // release root
    _root = nullptr;
    _renderTarget = nullptr;

    if (_renderService)
    {
        // tear down render service
        _renderService->teardown(_graphics);

        // try removing render objects
        if (shared_from_this())
            _renderService->removeRenderObject(std::dynamic_pointer_cast<g::irender_object>(shared_from_this()));

        if (_varConsole)
            _renderService->removeRenderObject(std::dynamic_pointer_cast<g::irender_object>(_varConsole));
        
        if (_debugConsole)
            _renderService->removeRenderObject(std::dynamic_pointer_cast<g::irender_object>(_debugConsole));
    }

    // release settings service
    b::services::set<journal_app>(std::shared_ptr<isettings_service>());
    _settingsService = nullptr;

    // release debug console
    b::services::set<journal_app>(std::shared_ptr<a::idebug_console>());
    _debugConsole = nullptr;
    _debugRoot = nullptr;

    // release var console and var groups
    b::services::set<journal_app>(std::shared_ptr<a::ivar_console>());
    _varConsole = nullptr;
    _varRoot = nullptr;

    // release render service
    b::services::set<journal_app>(std::shared_ptr<g::irender_service>());
    _renderService = nullptr;

    // release graphics
    b::services::set<journal_app>(std::shared_ptr<g::iengine>());
    _graphics = nullptr;

    // release performance service
    b::services::set<journal_app>(std::shared_ptr<iperformance_service>());
    _performanceService = nullptr;

    // release memory tracking service
    b::services::set<journal_app>(std::shared_ptr<jsm::imemory_tracking_service>());
    _memoryTrackingService = nullptr;

    // release storage service
    b::services::set<journal_app>(std::shared_ptr<jss::istorage_service>());
    _storageService = nullptr;

    // release undo redo service
    b::services::set<journal_app>(std::shared_ptr<bt::itransition_tracking_service>());
    _undoRedoService = nullptr;

    // release automation service
    b::services::set<journal_app>(std::shared_ptr<jsa::iautomation_service>());
    _automationService = nullptr;
}

void journal_app::onCommand(const view::command_id id, void* context)
{
    ACQUIRE_VIEW_LOCK;

    if (id == view::command_id::copy)
    {
        _winrtFeatures->copy(false); // no selection, copy the whole page
    }
    if (id == view::command_id::copy_selection)
    {
        _winrtFeatures->copy(true);
    }
    else if (id == view::command_id::paste)
    {
        _winrtFeatures->paste();
    }
    else if (id == view::command_id::photo_camera)
    {
        _winrtFeatures->selectCameraPhoto();
    }
    else if (id == view::command_id::photo_filesystem)
    {
        _winrtFeatures->selectFilesystemPhoto();
    }
    _root->onCommand(id, context);
}

void journal_app::printCredits()
{
    baja::app::debugConsoleTrace(boost::wformat(L"%s credits (in alphabetical order)") % constants::general::applicationName(), TRACE_TAG_ALWAYS);
    baja::app::debugConsoleTrace(L"-------------------------------------------------------", TRACE_TAG_ALWAYS);
    baja::app::debugConsoleTrace(L"Amit Agarwal", TRACE_TAG_ALWAYS);
    baja::app::debugConsoleTrace(L"Vikas Bhatia", TRACE_TAG_ALWAYS);
    baja::app::debugConsoleTrace(L"Alan Chan", TRACE_TAG_ALWAYS);
    baja::app::debugConsoleTrace(L"Xiaoji Chen", TRACE_TAG_ALWAYS);
    baja::app::debugConsoleTrace(L"Eric Brumer", TRACE_TAG_ALWAYS);
    baja::app::debugConsoleTrace(L"Julio Estrada", TRACE_TAG_ALWAYS);
    baja::app::debugConsoleTrace(L"Justin Goshi", TRACE_TAG_ALWAYS);
    baja::app::debugConsoleTrace(L"Benjamin Grefsrud", TRACE_TAG_ALWAYS);
    baja::app::debugConsoleTrace(L"Boris Jabes", TRACE_TAG_ALWAYS);
    baja::app::debugConsoleTrace(L"Scott Marison", TRACE_TAG_ALWAYS);
    baja::app::debugConsoleTrace(L"George Mileka", TRACE_TAG_ALWAYS);
    baja::app::debugConsoleTrace(L"Jorge Pereira", TRACE_TAG_ALWAYS);
}

std::shared_ptr<a::iapp> createApp(
    const std::shared_ptr<a::iloopless_app_shell>& shell,
    const std::shared_ptr<a::igraphics_factory>& graphicsFactory
    )
{
    std::shared_ptr<journal_app> returnValue = std::make_shared<journal_app>(shell, graphicsFactory);
    return std::static_pointer_cast<a::iapp>(returnValue);
}

} // namespace journal